# -*- coding: utf-8 -*-
"""
Module for managing the LXD daemon and its containers.

.. versionadded:: 2019.2.0

`LXD(1)`_ is a container "hypervisor". This execution module provides
several functions to help manage it and its containers.

.. note::

    - `pylxd(2)`_ version >=2.2.5 is required to let this work,
      currently only available via pip.

        To install on Ubuntu:

        $ apt-get install libssl-dev python-pip
        $ pip install -U pylxd

    - you need lxd installed on the minion
      for the init() and version() methods.

    - for the config_get() and config_get() methods
      you need to have lxd-client installed.

.. _LXD(1): https://linuxcontainers.org/lxd/
.. _pylxd(2): https://github.com/lxc/pylxd/blob/master/doc/source/installation.rst

:maintainer: René Jochum <rene@jochums.at>
:maturity: new
:depends: python-pylxd
:platform: Linux
"""

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals

# Set up logging
import logging
import os
from datetime import datetime

import salt.ext.six as six

# Import salt libs
import salt.utils.decorators.path
import salt.utils.files
from salt.exceptions import CommandExecutionError, SaltInvocationError
from salt.ext.six.moves import map, zip
from salt.utils.versions import LooseVersion

# Import 3rd-party libs
try:
    import pylxd

    HAS_PYLXD = True

    import urllib3

    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
except ImportError:
    HAS_PYLXD = False


log = logging.getLogger(__name__)

__docformat__ = "restructuredtext en"

_pylxd_minimal_version = "2.2.5"

# Keep in sync with: https://github.com/lxc/lxd/blob/master/shared/osarch/architectures.go
_architectures = {
    "unknown": "0",
    "i686": "1",
    "x86_64": "2",
    "armv7l": "3",
    "aarch64": "4",
    "ppc": "5",
    "ppc64": "6",
    "ppc64le": "7",
    "s390x": "8",
}

# Keep in sync with: https://github.com/lxc/lxd/blob/master/shared/api/status_code.go
CONTAINER_STATUS_RUNNING = 103

__virtualname__ = "lxd"

_connection_pool = {}


def __virtual__():
    if HAS_PYLXD:
        if LooseVersion(pylxd_version()) < LooseVersion(_pylxd_minimal_version):
            return (
                False,
                (
                    "The lxd execution module cannot be loaded:"
                    ' pylxd "{0}" is not supported,'
                    ' you need at least pylxd "{1}"'
                ).format(pylxd_version(), _pylxd_minimal_version),
            )

        return __virtualname__

    return (
        False,
        (
            "The lxd execution module cannot be loaded: "
            "the pylxd python module is not available."
        ),
    )


################
# LXD Management
################
@salt.utils.decorators.path.which("lxd")
def version():
    """
    Returns the actual lxd version.

    CLI Example:

    .. code-block:: bash

        salt '*' lxd.version

    """
    return __salt__["cmd.run"]("lxd --version")


def pylxd_version():
    """
    Returns the actual pylxd version.

    CLI Example:

    .. code-block:: bash

        salt '*' lxd.pylxd_version

    """
    return pylxd.__version__


@salt.utils.decorators.path.which("lxd")
def init(
    storage_backend="dir",
    trust_password=None,
    network_address=None,
    network_port=None,
    storage_create_device=None,
    storage_create_loop=None,
    storage_pool=None,
):
    """
    Calls lxd init --auto -- opts

    storage_backend :
        Storage backend to use (zfs or dir, default: dir)

    trust_password :
        Password required to add new clients

    network_address : None
        Address to bind LXD to (default: none)

    network_port : None
        Port to bind LXD to (Default: 8443)

    storage_create_device : None
        Setup device based storage using this DEVICE

    storage_create_loop : None
        Setup loop based storage with this SIZE in GB

    storage_pool : None
        Storage pool to use or create

    CLI Examples:

    To listen on all IPv4/IPv6 Addresses:

    .. code-block:: bash

        salt '*' lxd.init dir PaSsW0rD [::]

    To not listen on Network:

    .. code-block:: bash

        salt '*' lxd.init
    """

    cmd = ("lxd init --auto" ' --storage-backend="{0}"').format(storage_backend)

    if trust_password is not None:
        cmd = cmd + ' --trust-password="{0}"'.format(trust_password)

    if network_address is not None:
        cmd = cmd + ' --network-address="{0}"'.format(network_address)

    if network_port is not None:
        cmd = cmd + ' --network-port="{0}"'.format(network_port)

    if storage_create_device is not None:
        cmd = cmd + ' --storage-create-device="{0}"'.format(storage_create_device)

    if storage_create_loop is not None:
        cmd = cmd + ' --storage-create-loop="{0}"'.format(storage_create_loop)

    if storage_pool is not None:
        cmd = cmd + ' --storage-pool="{0}"'.format(storage_pool)

    try:
        output = __salt__["cmd.run"](cmd)
    except ValueError as e:
        raise CommandExecutionError(
            "Failed to call: '{0}', error was: {1}".format(cmd, six.text_type(e)),
        )

    if "error:" in output:
        raise CommandExecutionError(output[output.index("error:") + 7 :],)

    return output


@salt.utils.decorators.path.which("lxd")
@salt.utils.decorators.path.which("lxc")
def config_set(key, value):
    """
    Set an LXD daemon config option

    CLI Examples:

    To listen on IPv4 and IPv6 port 8443,
    you can omit the :8443 its the default:

    .. code-block:: bash

        salt '*' lxd.config_set core.https_address [::]:8443

    To set the server trust password:

    .. code-block:: bash

        salt '*' lxd.config_set core.trust_password blah

    """
    cmd = 'lxc config set "{0}" "{1}"'.format(key, value,)

    output = __salt__["cmd.run"](cmd)
    if "error:" in output:
        raise CommandExecutionError(output[output.index("error:") + 7 :],)

    return ('Config value "{0}" successfully set.'.format(key),)


@salt.utils.decorators.path.which("lxd")
@salt.utils.decorators.path.which("lxc")
def config_get(key):
    """
    Get an LXD daemon config option

    key :
        The key of the config value to retrieve

    CLI Examples:

    .. code-block:: bash

        salt '*' lxd.config_get core.https_address
    """

    cmd = 'lxc config get "{0}"'.format(key)

    output = __salt__["cmd.run"](cmd)
    if "error:" in output:
        raise CommandExecutionError(output[output.index("error:") + 7 :],)

    return output


#######################
# Connection Management
#######################
def pylxd_client_get(remote_addr=None, cert=None, key=None, verify_cert=True):
    """
    Get an pyxld client, this is not meant to be run over the CLI.

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if you
        provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    See the `requests-docs`_ for the SSL stuff.

    .. _requests-docs: http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification

    """

    pool_key = "|".join(
        (
            six.text_type(remote_addr),
            six.text_type(cert),
            six.text_type(key),
            six.text_type(verify_cert),
        )
    )

    if pool_key in _connection_pool:
        log.debug('Returning the client "%s" from our connection pool', remote_addr)
        return _connection_pool[pool_key]

    try:
        if remote_addr is None or remote_addr == "/var/lib/lxd/unix.socket":
            log.debug("Trying to connect to the local unix socket")
            client = pylxd.Client()
        else:
            if remote_addr.startswith("/"):
                client = pylxd.Client(remote_addr)
            else:
                if cert is None or key is None:
                    raise SaltInvocationError(
                        (
                            "You have to give a Cert and "
                            "Key file for remote endpoints."
                        )
                    )

                cert = os.path.expanduser(cert)
                key = os.path.expanduser(key)

                if not os.path.isfile(cert):
                    raise SaltInvocationError(
                        (
                            'You have given an invalid cert path: "{0}", '
                            "the file does not exists or is not a file."
                        ).format(cert)
                    )

                if not os.path.isfile(key):
                    raise SaltInvocationError(
                        (
                            'You have given an invalid key path: "{0}", '
                            "the file does not exists or is not a file."
                        ).format(key)
                    )

                log.debug(
                    'Trying to connect to "%s" with cert "%s", key "%s" and '
                    'verify_cert "%s"',
                    remote_addr,
                    cert,
                    key,
                    verify_cert,
                )
                client = pylxd.Client(
                    endpoint=remote_addr, cert=(cert, key,), verify=verify_cert
                )
    except pylxd.exceptions.ClientConnectionFailed:
        raise CommandExecutionError("Failed to connect to '{0}'".format(remote_addr))

    except TypeError as e:
        # Happens when the verification failed.
        raise CommandExecutionError(
            (
                'Failed to connect to "{0}",'
                " looks like the SSL verification failed, error was: {1}"
            ).format(remote_addr, six.text_type(e))
        )

    _connection_pool[pool_key] = client

    return client


def pylxd_save_object(obj):
    """ Saves an object (profile/image/container) and
        translate its execpetion on failure

    obj :
        The object to save

    This is an internal method, no CLI Example.
    """
    try:
        obj.save()
    except pylxd.exceptions.LXDAPIException as e:
        raise CommandExecutionError(six.text_type(e))

    return True


def authenticate(remote_addr, password, cert, key, verify_cert=True):
    """
    Authenticate with a remote LXDaemon.

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if you
        provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443

    password :
        The password of the remote.

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    CLI Example:

    .. code-block:: bash

        $ salt '*' lxd.authenticate https://srv01:8443 <yourpass> ~/.config/lxc/client.crt ~/.config/lxc/client.key false

    See the `requests-docs`_ for the SSL stuff.

    .. _requests-docs: http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification

    """
    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    if client.trusted:
        return True

    try:
        client.authenticate(password)
    except pylxd.exceptions.LXDAPIException as e:
        # Wrong password
        raise CommandExecutionError(six.text_type(e))

    return client.trusted


######################
# Container Management
######################
def container_list(
    list_names=False, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Lists containers

    list_names : False
        Only return a list of names when True

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    CLI Examples:

    Full dict with all available information:

    .. code-block:: bash

        salt '*' lxd.container_list

    For a list of names:

    .. code-block:: bash

        salt '*' lxd.container_list true

    See also `container-attributes`_.

    .. _container-attributes: https://github.com/lxc/pylxd/blob/master/doc/source/containers.rst#container-attributes

    """

    client = pylxd_client_get(remote_addr, cert, key, verify_cert)
    containers = client.containers.all()
    if list_names:
        return [c.name for c in containers]

    return map(_pylxd_model_to_dict, containers)


def container_create(
    name,
    source,
    profiles=None,
    config=None,
    devices=None,
    architecture="x86_64",
    ephemeral=False,
    wait=True,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
    _raw=False,
):
    """
    Create a container

    name :
        The name of the container

    source :
        Can be either a string containing an image alias:
             "xenial/amd64"

        or an dict with type "image" with alias:
            {"type": "image",
             "alias": "xenial/amd64"}

        or image with "fingerprint":
            {"type": "image",
             "fingerprint": "SHA-256"}

        or image with "properties":
            {"type": "image",
             "properties": {
                "os": "ubuntu",
                "release": "14.04",
                "architecture": "x86_64"}}

        or none:
            {"type": "none"}

        or copy:
            {"type": "copy",
             "source": "my-old-container"}

    profiles : ['default']
        List of profiles to apply on this container

    config :
        A config dict or None (None = unset).

        Can also be a list:
            [{'key': 'boot.autostart', 'value': 1},
             {'key': 'security.privileged', 'value': '1'}]

    devices :
        A device dict or None (None = unset).

    architecture : 'x86_64'
        Can be one of the following:
            * unknown
            * i686
            * x86_64
            * armv7l
            * aarch64
            * ppc
            * ppc64
            * ppc64le
            * s390x

    ephemeral : False
        Destroy this container after stop?

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    _raw : False
        Return the raw pyxld object or a dict?

    CLI Examples:

    .. code-block:: bash

        salt '*' lxd.container_create test xenial/amd64

    See also the `rest-api-docs`_.

    .. _rest-api-docs: https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1

    """
    if profiles is None:
        profiles = ["default"]

    if config is None:
        config = {}

    if devices is None:
        devices = {}

    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    if not isinstance(profiles, (list, tuple, set,)):
        raise SaltInvocationError("'profiles' must be formatted as list/tuple/set.")

    if architecture not in _architectures:
        raise SaltInvocationError(
            ("Unknown architecture '{0}' " "given for the container '{1}'").format(
                architecture, name
            )
        )

    if isinstance(source, six.string_types):
        source = {"type": "image", "alias": source}

    config, devices = normalize_input_values(config, devices)

    try:
        container = client.containers.create(
            {
                "name": name,
                "architecture": _architectures[architecture],
                "profiles": profiles,
                "source": source,
                "config": config,
                "ephemeral": ephemeral,
            },
            wait=wait,
        )
    except pylxd.exceptions.LXDAPIException as e:
        raise CommandExecutionError(six.text_type(e))

    if not wait:
        return container.json()["operation"]

    # Add devices if not wait and devices have been given.
    if devices:
        for dn, dargs in six.iteritems(devices):
            container_device_add(name, dn, **dargs)

    if _raw:
        return container

    return _pylxd_model_to_dict(container)


def container_get(
    name=None, remote_addr=None, cert=None, key=None, verify_cert=True, _raw=False
):
    """ Gets a container from the LXD

        name :
            The name of the container to get.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        _raw :
            Return the pylxd object, this is internal and by states in use.
    """
    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    if name is None:
        containers = client.containers.all()
        if _raw:
            return containers
    else:
        containers = []
        try:
            containers = [client.containers.get(name)]
        except pylxd.exceptions.LXDAPIException:
            raise SaltInvocationError("Container '{0}' not found".format(name))
        if _raw:
            return containers[0]

    infos = []
    for container in containers:
        infos.append(dict([(container.name, _pylxd_model_to_dict(container))]))
    return infos


def container_delete(name, remote_addr=None, cert=None, key=None, verify_cert=True):
    """
    Delete a container

    name :
        Name of the container to delete

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    container.delete(wait=True)
    return True


def container_rename(
    name, newname, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Rename a container

    name :
        Name of the container to Rename

    newname :
        The new name of the container

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    if container.status_code == CONTAINER_STATUS_RUNNING:
        raise SaltInvocationError(
            "Can't rename the running container '{0}'.".format(name)
        )

    container.rename(newname, wait=True)
    return _pylxd_model_to_dict(container)


def container_state(name=None, remote_addr=None, cert=None, key=None, verify_cert=True):
    """
    Get container state

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    if name is None:
        containers = client.containers.all()
    else:
        try:
            containers = [client.containers.get(name)]
        except pylxd.exceptions.LXDAPIException:
            raise SaltInvocationError("Container '{0}' not found".format(name))

    states = []
    for container in containers:
        state = {}
        state = container.state()

        states.append(
            dict(
                [
                    (
                        container.name,
                        dict(
                            [
                                (k, getattr(state, k))
                                for k in dir(state)
                                if not k.startswith("_")
                            ]
                        ),
                    )
                ]
            )
        )
    return states


def container_start(name, remote_addr=None, cert=None, key=None, verify_cert=True):
    """
    Start a container

    name :
        Name of the container to start

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    container.start(wait=True)
    return _pylxd_model_to_dict(container)


def container_stop(
    name,
    timeout=30,
    force=True,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
):
    """
    Stop a container

    name :
        Name of the container to stop

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    container.stop(timeout, force, wait=True)
    return _pylxd_model_to_dict(container)


def container_restart(name, remote_addr=None, cert=None, key=None, verify_cert=True):
    """
    Restart a container

    name :
        Name of the container to restart

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    container.restart(wait=True)
    return _pylxd_model_to_dict(container)


def container_freeze(name, remote_addr=None, cert=None, key=None, verify_cert=True):
    """
    Freeze a container

    name :
        Name of the container to freeze

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    container.freeze(wait=True)
    return _pylxd_model_to_dict(container)


def container_unfreeze(name, remote_addr=None, cert=None, key=None, verify_cert=True):
    """
    Unfreeze a container

    name :
        Name of the container to unfreeze

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    container.unfreeze(wait=True)
    return _pylxd_model_to_dict(container)


def container_migrate(
    name,
    stop_and_start=False,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
    src_remote_addr=None,
    src_cert=None,
    src_key=None,
    src_verify_cert=None,
):
    """ Migrate a container.

        If the container is running, it either must be shut down
        first (use stop_and_start=True) or criu must be installed
        on the source and destination machines.

        For this operation both certs need to be authenticated,
        use :mod:`lxd.authenticate <salt.modules.lxd.authenticate`
        to authenticate your cert(s).

        name :
            Name of the container to migrate

        stop_and_start :
            Stop the container on the source and start it on dest

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Example:

        .. code-block:: bash

            # Authorize
            salt '*' lxd.authenticate https://srv01:8443 <yourpass> ~/.config/lxc/client.crt ~/.config/lxc/client.key false
            salt '*' lxd.authenticate https://srv02:8443 <yourpass> ~/.config/lxc/client.crt ~/.config/lxc/client.key false

            # Migrate phpmyadmin from srv01 to srv02
            salt '*' lxd.container_migrate phpmyadmin stop_and_start=true remote_addr=https://srv02:8443 cert=~/.config/lxc/client.crt key=~/.config/lxc/client.key verify_cert=False src_remote_addr=https://srv01:8443
    """
    if src_cert is None:
        src_cert = cert

    if src_key is None:
        src_key = key

    if src_verify_cert is None:
        src_verify_cert = verify_cert

    container = container_get(
        name, src_remote_addr, src_cert, src_key, src_verify_cert, _raw=True
    )

    dest_client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    for pname in container.profiles:
        try:
            dest_client.profiles.get(pname)
        except pylxd.exceptions.LXDAPIException:
            raise SaltInvocationError(
                "not all the profiles from the source exist on the target"
            )

    was_running = container.status_code == CONTAINER_STATUS_RUNNING
    if stop_and_start and was_running:
        container.stop(wait=True)

    try:
        dest_container = container.migrate(dest_client, wait=True)
        dest_container.profiles = container.profiles
        dest_container.save()
    except pylxd.exceptions.LXDAPIException as e:
        raise CommandExecutionError(six.text_type(e))

    # Remove the source container
    container.delete(wait=True)

    if stop_and_start and was_running:
        dest_container.start(wait=True)

    return _pylxd_model_to_dict(dest_container)


def container_config_get(
    name, config_key, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Get a container config value

    name :
        Name of the container

    config_key :
        The config key to retrieve

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    return _get_property_dict_item(container, "config", config_key)


def container_config_set(
    name,
    config_key,
    config_value,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
):
    """
    Set a container config value

    name :
        Name of the container

    config_key :
        The config key to set

    config_value :
        The config value to set

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _set_property_dict_item(container, "config", config_key, config_value)


def container_config_delete(
    name, config_key, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Delete a container config value

    name :
        Name of the container

    config_key :
        The config key to delete

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _delete_property_dict_item(container, "config", config_key)


def container_device_get(
    name, device_name, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Get a container device

    name :
        Name of the container

    device_name :
        The device name to retrieve

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _get_property_dict_item(container, "devices", device_name)


def container_device_add(
    name,
    device_name,
    device_type="disk",
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
    **kwargs
):
    """
    Add a container device

    name :
        Name of the container

    device_name :
        The device name to add

    device_type :
        Type of the device

    ** kwargs :
        Additional device args

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    kwargs["type"] = device_type
    return _set_property_dict_item(container, "devices", device_name, kwargs)


def container_device_delete(
    name, device_name, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Delete a container device

    name :
        Name of the container

    device_name :
        The device name to delete

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.
    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _delete_property_dict_item(container, "devices", device_name)


def container_file_put(
    name,
    src,
    dst,
    recursive=False,
    overwrite=False,
    mode=None,
    uid=None,
    gid=None,
    saltenv="base",
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
):
    """
    Put a file into a container

    name :
        Name of the container

    src :
        The source file or directory

    dst :
        The destination file or directory

    recursive :
        Decent into src directory

    overwrite :
        Replace destination if it exists

    mode :
        Set file mode to octal number

    uid :
        Set file uid (owner)

    gid :
        Set file gid (group)

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    CLI Example:

    .. code-block:: bash

        salt '*' lxd.container_file_put <container name> /var/tmp/foo /var/tmp/

    """
    # Possibilities:
    #  (src, dst, dir, dir1, and dir2 are directories)
    #  cp /src/file1 /dst/file1
    #  cp /src/file1 /dst/file2
    #  cp /src/file1 /dst
    #  cp /src/file1 /dst/
    #  cp -r /src/dir /dst/
    #  cp -r /src/dir/ /dst/
    #  cp -r /src/dir1 /dst/dir2 (which is not /src/dir1 /dst/dir2/)
    #  cp -r /src/dir1 /dst/dir2/

    # Fix mode. Salt commandline doesn't use octals, so 0600 will be
    # the decimal integer 600 (and not the octal 0600). So, it it's
    # and integer, handle it as if it where a octal representation.
    mode = six.text_type(mode)
    if not mode.startswith("0"):
        mode = "0{0}".format(mode)

    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    src = os.path.expanduser(src)

    if not os.path.isabs(src):
        if src.find("://") >= 0:
            cached_file = __salt__["cp.cache_file"](src, saltenv=saltenv)
            if not cached_file:
                raise SaltInvocationError("File '{0}' not found".format(src))
            if not os.path.isabs(cached_file):
                raise SaltInvocationError("File path must be absolute.")
            src = cached_file

    # Make sure that src doesn't end with '/', unless it's '/'
    src = src.rstrip(os.path.sep)
    if not src:
        src = os.path.sep

    if not os.path.exists(src):
        raise CommandExecutionError("No such file or directory '{0}'".format(src))

    if os.path.isdir(src) and not recursive:
        raise SaltInvocationError(
            (
                "Cannot copy overwriting a directory "
                "without recursive flag set to true!"
            )
        )

    try:
        dst_is_directory = False
        container.files.get(os.path.join(dst, "."))
    except pylxd.exceptions.NotFound:
        pass
    except pylxd.exceptions.LXDAPIException as why:
        if six.text_type(why).find("Is a directory") >= 0:
            dst_is_directory = True

    if os.path.isfile(src):
        # Source is a file
        if dst_is_directory:
            dst = os.path.join(dst, os.path.basename(src))
            if not overwrite:
                found = True
                try:
                    container.files.get(os.path.join(dst))
                except pylxd.exceptions.NotFound:
                    found = False
                except pylxd.exceptions.LXDAPIException as why:
                    if six.text_type(why).find("not found") >= 0:
                        # Old version of pylxd
                        found = False
                    else:
                        raise
                if found:
                    raise SaltInvocationError(
                        "Destination exists and overwrite is false"
                    )
        if mode is not None or uid is not None or gid is not None:
            # Need to get file stats
            stat = os.stat(src)
            if mode is None:
                mode = oct(stat.st_mode)
            if uid is None:
                uid = stat.st_uid
            if gid is None:
                gid = stat.st_gid

        with salt.utils.files.fopen(src, "rb") as src_fp:
            container.files.put(dst, src_fp.read(), mode=mode, uid=uid, gid=gid)
        return True
    elif not os.path.isdir(src):
        raise SaltInvocationError("Source is neither file nor directory")

    # Source is a directory
    # idx for dstdir = dst + src[idx:]
    if dst.endswith(os.sep):
        idx = len(os.path.dirname(src))
    elif dst_is_directory:
        idx = len(src)
    else:
        # Destination is not a directory and doesn't end with '/'
        # Check that the parent directory of dst exists
        # and is a directory
        try:
            container.files.get(os.path.join(os.path.dirname(dst), "."))
        except pylxd.exceptions.NotFound:
            pass
        except pylxd.exceptions.LXDAPIException as why:
            if six.text_type(why).find("Is a directory") >= 0:
                dst_is_directory = True
                # destination is non-existent
                # cp -r /src/dir1 /scr/dir1
                # cp -r /src/dir1 /scr/dir2
                idx = len(src)
                overwrite = True

    # Copy src directory recursive
    if not overwrite:
        raise SaltInvocationError("Destination exists and overwrite is false")

    # Collect all directories first, to create them in one call
    # (for performance reasons)
    dstdirs = []
    for path, _, files in os.walk(src):
        dstdir = os.path.join(dst, path[idx:].lstrip(os.path.sep))
        dstdirs.append(dstdir)
    container.execute(["mkdir", "-p"] + dstdirs)

    set_mode = mode
    set_uid = uid
    set_gid = gid
    # Now transfer the files
    for path, _, files in os.walk(src):
        dstdir = os.path.join(dst, path[idx:].lstrip(os.path.sep))
        for name in files:
            src_name = os.path.join(path, name)
            dst_name = os.path.join(dstdir, name)

            if mode is not None or uid is not None or gid is not None:
                # Need to get file stats
                stat = os.stat(src_name)
                if mode is None:
                    set_mode = oct(stat.st_mode)
                if uid is None:
                    set_uid = stat.st_uid
                if gid is None:
                    set_gid = stat.st_gid

            with salt.utils.files.fopen(src_name, "rb") as src_fp:
                container.files.put(
                    dst_name, src_fp.read(), mode=set_mode, uid=set_uid, gid=set_gid
                )

    return True


def container_file_get(
    name,
    src,
    dst,
    overwrite=False,
    mode=None,
    uid=None,
    gid=None,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
):
    """
    Get a file from a container

    name :
        Name of the container

    src :
        The source file or directory

    dst :
        The destination file or directory

    mode :
        Set file mode to octal number

    uid :
        Set file uid (owner)

    gid :
        Set file gid (group)

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    """
    # Fix mode. Salt commandline doesn't use octals, so 0600 will be
    # the decimal integer 600 (and not the octal 0600). So, it it's
    # and integer, handle it as if it where a octal representation.

    # Do only if mode is not None, otherwise we get 0None
    if mode is not None:
        mode = six.text_type(mode)
        if not mode.startswith("0"):
            mode = "0{0}".format(mode)

    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    dst = os.path.expanduser(dst)
    if not os.path.isabs(dst):
        raise SaltInvocationError("File path must be absolute.")

    if os.path.isdir(dst):
        dst = os.path.join(dst, os.path.basename(src))
    elif not os.path.isdir(os.path.dirname(dst)):
        raise SaltInvocationError("Parent directory for destination doesn't exist.")

    if os.path.exists(dst):
        if not overwrite:
            raise SaltInvocationError("Destination exists and overwrite is false.")
        if not os.path.isfile(dst):
            raise SaltInvocationError("Destination exists but is not a file.")
    else:
        dst_path = os.path.dirname(dst)
        if not os.path.isdir(dst_path):
            raise CommandExecutionError(
                "No such file or directory '{0}'".format(dst_path)
            )
        # Seems to be duplicate of line 1794, produces /path/file_name/file_name
        # dst = os.path.join(dst, os.path.basename(src))

    with salt.utils.files.fopen(dst, "wb") as df:
        df.write(container.files.get(src))

    if mode:
        os.chmod(dst, mode)
    if uid or uid == "0":
        uid = int(uid)
    else:
        uid = -1
    if gid or gid == "0":
        gid = int(gid)
    else:
        gid = -1
    if uid != -1 or gid != -1:
        os.chown(dst, uid, gid)
    return True


def container_execute(
    name, cmd, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Execute a command list on a container.

    name :
        Name of the container

    cmd :
        Command to be executed (as a list)

        Example :
            '["ls", "-l"]'

    remote_addr :
        An URL to a remote Server, you also have to give cert and key if
        you provide remote_addr and its a TCP Address!

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    CLI Example:

    .. code-block:: bash

        salt '*' lxd.container_execute <container name> '["ls", "-l"]'

    """
    container = container_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    try:
        result = container.execute(cmd)
        saltresult = {}
        if not hasattr(result, "exit_code"):
            saltresult = dict(exit_code=0, stdout=result[0], stderr=result[1],)
        else:
            saltresult = dict(
                exit_code=result.exit_code, stdout=result.stdout, stderr=result.stderr,
            )
    except pylxd.exceptions.NotFound as e:
        # TODO: Using exit_code 0 here is not always right,
        # in the most cases the command worked ok though.
        # See: https://github.com/lxc/pylxd/issues/280
        saltresult = dict(exit_code=0, stdout="", stderr=six.text_type(e))

    if int(saltresult["exit_code"]) > 0:
        saltresult["result"] = False
    else:
        saltresult["result"] = True

    return saltresult


####################
# Profile Management
####################
def profile_list(
    list_names=False, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """ Lists all profiles from the LXD.

        list_names :

            Return a list of names instead of full blown dicts.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Examples:

        .. code-block:: bash

            salt '*' lxd.profile_list true --out=json
            salt '*' lxd.profile_list --out=json
    """

    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    profiles = client.profiles.all()
    if list_names:
        return [p.name for p in profiles]

    return map(_pylxd_model_to_dict, profiles)


def profile_create(
    name,
    config=None,
    devices=None,
    description=None,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
):
    """ Creates a profile.

        name :
            The name of the profile to get.

        config :
            A config dict or None (None = unset).

            Can also be a list:
                [{'key': 'boot.autostart', 'value': 1},
                 {'key': 'security.privileged', 'value': '1'}]

        devices :
            A device dict or None (None = unset).

        description :
            A description string or None (None = unset).

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Examples:

        .. code-block:: bash

            $ salt '*' lxd.profile_create autostart config="{boot.autostart: 1, boot.autostart.delay: 2, boot.autostart.priority: 1}"
            $ salt '*' lxd.profile_create shared_mounts devices="{shared_mount: {type: 'disk', source: '/home/shared', path: '/home/shared'}}"

        See the `lxd-docs`_ for the details about the config and devices dicts.

        .. _lxd-docs: https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-10
    """
    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    config, devices = normalize_input_values(config, devices)

    try:
        profile = client.profiles.create(name, config, devices)
    except pylxd.exceptions.LXDAPIException as e:
        raise CommandExecutionError(six.text_type(e))

    if description is not None:
        profile.description = description
        pylxd_save_object(profile)

    return _pylxd_model_to_dict(profile)


def profile_get(
    name, remote_addr=None, cert=None, key=None, verify_cert=True, _raw=False
):
    """ Gets a profile from the LXD

        name :
            The name of the profile to get.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        _raw :
            Return the pylxd object, this is internal and by states in use.

        CLI Examples:

        .. code-block:: bash

            $ salt '*' lxd.profile_get autostart
    """
    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    profile = None
    try:
        profile = client.profiles.get(name)
    except pylxd.exceptions.LXDAPIException:
        raise SaltInvocationError("Profile '{0}' not found".format(name))

    if _raw:
        return profile

    return _pylxd_model_to_dict(profile)


def profile_delete(name, remote_addr=None, cert=None, key=None, verify_cert=True):
    """ Deletes a profile.

        name :
            The name of the profile to delete.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Example:

        .. code-block:: bash

            $ salt '*' lxd.profile_delete shared_mounts
    """
    profile = profile_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    profile.delete()
    return True


def profile_config_get(
    name, config_key, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """ Get a profile config item.

        name :
            The name of the profile to get the config item from.

        config_key :
            The key for the item to retrieve.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Example:

        .. code-block:: bash

            $ salt '*' lxd.profile_config_get autostart boot.autostart
    """
    profile = profile_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _get_property_dict_item(profile, "config", config_key)


def profile_config_set(
    name,
    config_key,
    config_value,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
):
    """ Set a profile config item.

        name :
            The name of the profile to set the config item to.

        config_key :
            The items key.

        config_value :
            Its items value.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Example:

        .. code-block:: bash

            $ salt '*' lxd.profile_config_set autostart boot.autostart 0
    """
    profile = profile_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _set_property_dict_item(profile, "config", config_key, config_value)


def profile_config_delete(
    name, config_key, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """ Delete a profile config item.

        name :
            The name of the profile to delete the config item.

        config_key :
            The config key for the value to retrieve.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Example:

        .. code-block:: bash

            $ salt '*' lxd.profile_config_delete autostart boot.autostart.delay
    """
    profile = profile_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _delete_property_dict_item(profile, "config", config_key)


def profile_device_get(
    name, device_name, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """ Get a profile device.

        name :
            The name of the profile to get the device from.

        device_name :
            The name of the device to retrieve.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Example:

        .. code-block:: bash

            $ salt '*' lxd.profile_device_get default eth0
    """
    profile = profile_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _get_property_dict_item(profile, "devices", device_name)


def profile_device_set(
    name,
    device_name,
    device_type="disk",
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
    **kwargs
):
    """ Set a profile device.

        name :
            The name of the profile to set the device to.

        device_name :
            The name of the device to set.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Example:

        .. code-block:: bash

            $ salt '*' lxd.profile_device_set autostart eth1 nic nictype=bridged parent=lxdbr0
    """
    profile = profile_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    kwargs["type"] = device_type

    for k, v in six.iteritems(kwargs):
        kwargs[k] = six.text_type(v)

    return _set_property_dict_item(profile, "devices", device_name, kwargs)


def profile_device_delete(
    name, device_name, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """ Delete a profile device.

        name :
            The name of the profile to delete the device.

        device_name :
            The name of the device to delete.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Example:

        .. code-block:: bash

            $ salt '*' lxd.profile_device_delete autostart eth1

    """
    profile = profile_get(name, remote_addr, cert, key, verify_cert, _raw=True)

    return _delete_property_dict_item(profile, "devices", device_name)


##################
# Image Management
##################
def image_list(
    list_aliases=False, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """ Lists all images from the LXD.

        list_aliases :

            Return a dict with the fingerprint as key and
            a list of aliases as value instead.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Examples:

        .. code-block:: bash

            $ salt '*' lxd.image_list true --out=json
            $ salt '*' lxd.image_list --out=json
    """
    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    images = client.images.all()
    if list_aliases:
        return {i.fingerprint: [a["name"] for a in i.aliases] for i in images}

    return map(_pylxd_model_to_dict, images)


def image_get(
    fingerprint, remote_addr=None, cert=None, key=None, verify_cert=True, _raw=False
):
    """ Get an image by its fingerprint

        fingerprint :
            The fingerprint of the image to retrieve

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        _raw : False
            Return the raw pylxd object or a dict of it?

        CLI Examples:

        ..code-block:: bash

            $ salt '*' lxd.image_get <fingerprint>
    """
    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    image = None
    try:
        image = client.images.get(fingerprint)
    except pylxd.exceptions.LXDAPIException:
        raise SaltInvocationError(
            "Image with fingerprint '{0}' not found".format(fingerprint)
        )

    if _raw:
        return image

    return _pylxd_model_to_dict(image)


def image_get_by_alias(
    alias, remote_addr=None, cert=None, key=None, verify_cert=True, _raw=False
):
    """ Get an image by an alias

        alias :
            The alias of the image to retrieve

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        _raw : False
            Return the raw pylxd object or a dict of it?

        CLI Examples:

        ..code-block:: bash

            $ salt '*' lxd.image_get_by_alias xenial/amd64
    """
    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    image = None
    try:
        image = client.images.get_by_alias(alias)
    except pylxd.exceptions.LXDAPIException:
        raise SaltInvocationError("Image with alias '{0}' not found".format(alias))

    if _raw:
        return image

    return _pylxd_model_to_dict(image)


def image_delete(image, remote_addr=None, cert=None, key=None, verify_cert=True):
    """ Delete an image by an alias or fingerprint

        name :
            The alias or fingerprint of the image to delete,
            can be a obj for the states.

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Examples:

        ..code-block:: bash

            $ salt '*' lxd.image_delete xenial/amd64
    """

    image = _verify_image(image, remote_addr, cert, key, verify_cert)

    image.delete()
    return True


def image_from_simplestreams(
    server,
    alias,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
    aliases=None,
    public=False,
    auto_update=False,
    _raw=False,
):
    """ Create an image from simplestreams

        server :
            Simplestreams server URI

        alias :
            The alias of the image to retrieve

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        aliases : []
            List of aliases to append to the copied image

        public : False
            Make this image public available

        auto_update : False
            Should LXD auto update that image?

        _raw : False
            Return the raw pylxd object or a dict of the image?

        CLI Examples:

        ..code-block:: bash

            $ salt '*' lxd.image_from_simplestreams "https://cloud-images.ubuntu.com/releases" "trusty/amd64" aliases='["t", "trusty/amd64"]' auto_update=True
    """
    if aliases is None:
        aliases = []

    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    try:
        image = client.images.create_from_simplestreams(
            server, alias, public=public, auto_update=auto_update
        )
    except pylxd.exceptions.LXDAPIException as e:
        raise CommandExecutionError(six.text_type(e))

    # Aliases support
    for alias in aliases:
        image_alias_add(image, alias)

    if _raw:
        return image

    return _pylxd_model_to_dict(image)


def image_from_url(
    url,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
    aliases=None,
    public=False,
    auto_update=False,
    _raw=False,
):
    """ Create an image from an url

        url :
            The URL from where to download the image

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        aliases : []
            List of aliases to append to the copied image

        public : False
            Make this image public available

        auto_update : False
            Should LXD auto update that image?

        _raw : False
            Return the raw pylxd object or a dict of the image?

        CLI Examples:

        ..code-block:: bash

            $ salt '*' lxd.image_from_url https://dl.stgraber.org/lxd aliases='["busybox-amd64"]'
    """
    if aliases is None:
        aliases = []

    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    try:
        image = client.images.create_from_url(
            url, public=public, auto_update=auto_update
        )
    except pylxd.exceptions.LXDAPIException as e:
        raise CommandExecutionError(six.text_type(e))

    # Aliases support
    for alias in aliases:
        image_alias_add(image, alias)

    if _raw:
        return image

    return _pylxd_model_to_dict(image)


def image_from_file(
    filename,
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
    aliases=None,
    public=False,
    saltenv="base",
    _raw=False,
):
    """ Create an image from a file

        filename :
            The filename of the rootfs

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        aliases : []
            List of aliases to append to the copied image

        public : False
            Make this image public available

        saltenv : base
            The saltenv to use for salt:// copies

        _raw : False
            Return the raw pylxd object or a dict of the image?

        CLI Examples:

        ..code-block:: bash

            $ salt '*' lxd.image_from_file salt://lxd/files/busybox.tar.xz aliases=["busybox-amd64"]
    """
    if aliases is None:
        aliases = []

    cached_file = __salt__["cp.cache_file"](filename, saltenv=saltenv)
    data = b""
    with salt.utils.files.fopen(cached_file, "r+b") as fp:
        data = fp.read()

    client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    try:
        image = client.images.create(data, public=public, wait=True)
    except pylxd.exceptions.LXDAPIException as e:
        raise CommandExecutionError(six.text_type(e))

    # Aliases support
    for alias in aliases:
        image_alias_add(image, alias)

    if _raw:
        return image

    return _pylxd_model_to_dict(image)


def image_copy_lxd(
    source,
    src_remote_addr,
    src_cert,
    src_key,
    src_verify_cert,
    remote_addr,
    cert,
    key,
    verify_cert=True,
    aliases=None,
    public=None,
    auto_update=None,
    _raw=False,
):
    """ Copy an image from another LXD instance

    source :
        An alias or a fingerprint of the source.

    src_remote_addr :
        An URL to the source remote daemon

        Examples:
            https://mysourceserver.lan:8443

    src_cert :
        PEM Formatted SSL Certificate for the source

        Examples:
            ~/.config/lxc/client.crt

    src_key :
        PEM Formatted SSL Key for the source

        Examples:
            ~/.config/lxc/client.key

    src_verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    remote_addr :
        Address of the destination daemon

        Examples:
            https://mydestserver.lan:8443

    cert :
        PEM Formatted SSL Certificate for the destination

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key for the destination

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Wherever to verify the cert, this is by default True
        but in the most cases you want to set it off as LXD
        normally uses self-signed certificates.

    aliases : []
        List of aliases to append to the copied image

    public : None
        Make this image public available, None = copy source

    auto_update : None
        Wherever to auto-update from the original source, None = copy source

    _raw : False
        Return the raw pylxd object or a dict of the destination image?

    CLI Examples:

    .. code-block:: bash

        $ salt '*' lxd.image_copy_lxd xenial/amd64 https://srv01:8443 ~/.config/lxc/client.crt ~/.config/lxc/client.key false https://srv02:8443 ~/.config/lxc/client.crt ~/.config/lxc/client.key false aliases="['xenial/amd64']"
    """
    if aliases is None:
        aliases = []

    log.debug(
        'Trying to copy the image "%s" from "%s" to "%s"',
        source,
        src_remote_addr,
        remote_addr,
    )

    # This will fail with a SaltInvocationError if
    # the image doesn't exists on the source and with a CommandExecutionError
    # on connection problems.
    src_image = None
    try:
        src_image = image_get_by_alias(
            source, src_remote_addr, src_cert, src_key, src_verify_cert, _raw=True
        )
    except SaltInvocationError:
        src_image = image_get(
            source, src_remote_addr, src_cert, src_key, src_verify_cert, _raw=True
        )

    # Will fail with a CommandExecutionError on connection problems.
    dest_client = pylxd_client_get(remote_addr, cert, key, verify_cert)

    dest_image = src_image.copy(
        dest_client, public=public, auto_update=auto_update, wait=True
    )

    # Aliases support
    for alias in aliases:
        image_alias_add(dest_image, alias)

    if _raw:
        return dest_image

    return _pylxd_model_to_dict(dest_image)


def image_alias_add(
    image,
    alias,
    description="",
    remote_addr=None,
    cert=None,
    key=None,
    verify_cert=True,
):
    """ Create an alias on the given image

        image :
            An image alias, a fingerprint or a image object

        alias :
            The alias to add

        description :
            Description of the alias

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Examples:

        .. code-block:: bash

            $ salt '*' lxd.image_alias_add xenial/amd64 x "Short version of xenial/amd64"
    """
    image = _verify_image(image, remote_addr, cert, key, verify_cert)

    for alias_info in image.aliases:
        if alias_info["name"] == alias:
            return True
    image.add_alias(alias, description)

    return True


def image_alias_delete(
    image, alias, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """ Delete an alias (this is currently not restricted to the image)

        image :
            An image alias, a fingerprint or a image object

        alias :
            The alias to delete

        remote_addr :
            An URL to a remote Server, you also have to give cert and key if
            you provide remote_addr and its a TCP Address!

            Examples:
                https://myserver.lan:8443
                /var/lib/mysocket.sock

        cert :
            PEM Formatted SSL Certificate.

            Examples:
                ~/.config/lxc/client.crt

        key :
            PEM Formatted SSL Key.

            Examples:
                ~/.config/lxc/client.key

        verify_cert : True
            Wherever to verify the cert, this is by default True
            but in the most cases you want to set it off as LXD
            normally uses self-signed certificates.

        CLI Examples:

        .. code-block:: bash

            $ salt '*' lxd.image_alias_add xenial/amd64 x "Short version of xenial/amd64"
    """
    image = _verify_image(image, remote_addr, cert, key, verify_cert)

    try:
        image.delete_alias(alias)
    except pylxd.exceptions.LXDAPIException:
        return False

    return True


#####################
# Snapshot Management
#####################


def snapshots_all(container, remote_addr=None, cert=None, key=None, verify_cert=True):
    """
    Get all snapshots for a container

    container :
        The name of the container to get.

    remote_addr :
        An URL to a remote server. The 'cert' and 'key' fields must also be
        provided if 'remote_addr' is defined.

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Verify the ssl certificate.  Default: True

    CLI Examples:

    .. code-block:: bash

        $ salt '*' lxd.snapshots_all test-container
    """
    containers = container_get(
        container, remote_addr, cert, key, verify_cert, _raw=True
    )
    if container:
        containers = [containers]
    ret = {}
    for cont in containers:
        ret.update({cont.name: [{"name": c.name} for c in cont.snapshots.all()]})

    return ret


def snapshots_create(
    container, name=None, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Create a snapshot for a container

    container :
        The name of the container to get.

    name :
        The name of the snapshot.

    remote_addr :
        An URL to a remote server. The 'cert' and 'key' fields must also be
        provided if 'remote_addr' is defined.

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Verify the ssl certificate.  Default: True

    CLI Examples:

    .. code-block:: bash

        $ salt '*' lxd.snapshots_create test-container test-snapshot
    """
    cont = container_get(container, remote_addr, cert, key, verify_cert, _raw=True)
    if not name:
        name = datetime.now().strftime("%Y%m%d%H%M%S")

    cont.snapshots.create(name)

    for c in snapshots_all(container).get(container):
        if c.get("name") == name:
            return {"name": name}

    return {"name": False}


def snapshots_delete(
    container, name, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Delete a snapshot for a container

    container :
        The name of the container to get.

    name :
        The name of the snapshot.

    remote_addr :
        An URL to a remote server. The 'cert' and 'key' fields must also be
        provided if 'remote_addr' is defined.

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Verify the ssl certificate.  Default: True

    CLI Examples:

    .. code-block:: bash

        $ salt '*' lxd.snapshots_delete test-container test-snapshot
    """
    cont = container_get(container, remote_addr, cert, key, verify_cert, _raw=True)

    try:
        for s in cont.snapshots.all():
            if s.name == name:
                s.delete()
                return True
    except pylxd.exceptions.LXDAPIException:
        pass

    return False


def snapshots_get(
    container, name, remote_addr=None, cert=None, key=None, verify_cert=True
):
    """
    Get information about snapshot for a container

    container :
        The name of the container to get.

    name :
        The name of the snapshot.

    remote_addr :
        An URL to a remote server. The 'cert' and 'key' fields must also be
        provided if 'remote_addr' is defined.

        Examples:
            https://myserver.lan:8443
            /var/lib/mysocket.sock

    cert :
        PEM Formatted SSL Certificate.

        Examples:
            ~/.config/lxc/client.crt

    key :
        PEM Formatted SSL Key.

        Examples:
            ~/.config/lxc/client.key

    verify_cert : True
        Verify the ssl certificate.  Default: True

    CLI Examples:

    .. code-block:: bash

        $ salt '*' lxd.snapshots_get test-container test-snapshot
    """
    container = container_get(container, remote_addr, cert, key, verify_cert, _raw=True)
    return container.snapshots.get(name)


################
# Helper Methods
################


def normalize_input_values(config, devices):
    """
    normalize config input so returns can be put into mongodb, which doesn't like `.`

    This is not meant to be used on the commandline.

    CLI Examples:

    .. code-block:: bash

        salt '*' lxd.normalize_input_values config={} devices={}
    """
    if isinstance(config, list):
        if config and "key" in config[0] and "value" in config[0]:
            config = {d["key"]: d["value"] for d in config}
        else:
            config = {}

    if isinstance(config, six.string_types):
        raise SaltInvocationError("config can't be a string, validate your YAML input.")

    if isinstance(devices, six.string_types):
        raise SaltInvocationError(
            "devices can't be a string, validate your YAML input."
        )

    # Golangs wants strings
    if config is not None:
        for k, v in six.iteritems(config):
            config[k] = six.text_type(v)
    if devices is not None:
        for dn in devices:
            for k, v in six.iteritems(devices[dn]):
                devices[dn][k] = v

    return (
        config,
        devices,
    )


def sync_config_devices(obj, newconfig, newdevices, test=False):
    """ Syncs the given config and devices with the object
        (a profile or a container)
        returns a changes dict with all changes made.

        obj :
            The object to sync with / or just test with.

        newconfig:
            The new config to check with the obj.

        newdevices:
            The new devices to check with the obj.

        test:
            Wherever to not change anything and give "Would change" message.
    """
    changes = {}

    #
    # config changes
    #
    if newconfig is None:
        newconfig = {}

    newconfig = dict(
        list(
            zip(
                map(six.text_type, newconfig.keys()),
                map(six.text_type, newconfig.values()),
            )
        )
    )
    cck = set(newconfig.keys())

    obj.config = dict(
        list(
            zip(
                map(six.text_type, obj.config.keys()),
                map(six.text_type, obj.config.values()),
            )
        )
    )
    ock = set(obj.config.keys())

    config_changes = {}
    # Removed keys
    for k in ock.difference(cck):
        # Ignore LXD internals.
        if k.startswith("volatile.") or k.startswith("image."):
            continue

        if not test:
            config_changes[k] = (
                'Removed config key "{0}", its value was "{1}"'
            ).format(k, obj.config[k])
            del obj.config[k]
        else:
            config_changes[k] = (
                'Would remove config key "{0} with value "{1}"'
            ).format(k, obj.config[k])

    # same keys
    for k in cck.intersection(ock):
        # Ignore LXD internals.
        if k.startswith("volatile.") or k.startswith("image."):
            continue

        if newconfig[k] != obj.config[k]:
            if not test:
                config_changes[k] = (
                    'Changed config key "{0}" to "{1}", ' 'its value was "{2}"'
                ).format(k, newconfig[k], obj.config[k])
                obj.config[k] = newconfig[k]
            else:
                config_changes[k] = (
                    'Would change config key "{0}" to "{1}", '
                    'its current value is "{2}"'
                ).format(k, newconfig[k], obj.config[k])

    # New keys
    for k in cck.difference(ock):
        # Ignore LXD internals.
        if k.startswith("volatile.") or k.startswith("image."):
            continue

        if not test:
            config_changes[k] = ('Added config key "{0}" = "{1}"').format(
                k, newconfig[k]
            )
            obj.config[k] = newconfig[k]
        else:
            config_changes[k] = ('Would add config key "{0}" = "{1}"').format(
                k, newconfig[k]
            )

    if config_changes:
        changes["config"] = config_changes

    #
    # devices changes
    #
    if newdevices is None:
        newdevices = {}

    dk = set(obj.devices.keys())
    ndk = set(newdevices.keys())

    devices_changes = {}
    # Removed devices
    for k in dk.difference(ndk):
        # Ignore LXD internals.
        if k == "root":
            continue

        if not test:
            devices_changes[k] = ('Removed device "{0}"').format(k)
            del obj.devices[k]
        else:
            devices_changes[k] = ('Would remove device "{0}"').format(k)

    # Changed devices
    for k, v in six.iteritems(obj.devices):
        # Ignore LXD internals also for new devices.
        if k == "root":
            continue

        if k not in newdevices:
            # In test mode we don't delete devices above.
            continue

        if newdevices[k] != v:
            if not test:
                devices_changes[k] = ('Changed device "{0}"').format(k)
                obj.devices[k] = newdevices[k]
            else:
                devices_changes[k] = ('Would change device "{0}"').format(k)

    # New devices
    for k in ndk.difference(dk):
        # Ignore LXD internals.
        if k == "root":
            continue

        if not test:
            devices_changes[k] = ('Added device "{0}"').format(k)
            obj.devices[k] = newdevices[k]
        else:
            devices_changes[k] = ('Would add device "{0}"').format(k)

    if devices_changes:
        changes["devices"] = devices_changes

    return changes


def _set_property_dict_item(obj, prop, key, value):
    """ Sets the dict item key of the attr from obj.

        Basicaly it does getattr(obj, prop)[key] = value.


        For the disk device we added some checks to make
        device changes on the CLI saver.
    """
    attr = getattr(obj, prop)
    if prop == "devices":
        device_type = value["type"]

        if device_type == "disk":

            if "path" not in value:
                raise SaltInvocationError("path must be given as parameter")

            if value["path"] != "/" and "source" not in value:
                raise SaltInvocationError("source must be given as parameter")

        for k in value.keys():
            if k.startswith("__"):
                del value[k]

        attr[key] = value

    else:  # config
        attr[key] = six.text_type(value)

    pylxd_save_object(obj)

    return _pylxd_model_to_dict(obj)


def _get_property_dict_item(obj, prop, key):
    attr = getattr(obj, prop)
    if key not in attr:
        raise SaltInvocationError("'{0}' doesn't exists".format(key))

    return attr[key]


def _delete_property_dict_item(obj, prop, key):
    attr = getattr(obj, prop)
    if key not in attr:
        raise SaltInvocationError("'{0}' doesn't exists".format(key))

    del attr[key]
    pylxd_save_object(obj)

    return True


def _verify_image(image, remote_addr=None, cert=None, key=None, verify_cert=True):
    # Get image by alias/fingerprint or check for fingerprint attribute
    if isinstance(image, six.string_types):
        name = image

        # This will fail with a SaltInvocationError if
        # the image doesn't exists on the source and with a
        # CommandExecutionError on connection problems.
        image = None
        try:
            image = image_get_by_alias(
                name, remote_addr, cert, key, verify_cert, _raw=True
            )
        except SaltInvocationError:
            image = image_get(name, remote_addr, cert, key, verify_cert, _raw=True)
    elif not hasattr(image, "fingerprint"):
        raise SaltInvocationError("Invalid image '{0}'".format(image))
    return image


def _pylxd_model_to_dict(obj):
    """Translates a plyxd model object to a dict"""
    marshalled = {}
    for key in obj.__attributes__.keys():
        if hasattr(obj, key):
            marshalled[key] = getattr(obj, key)
    return marshalled


#
# Monkey patching for missing functionality in pylxd
#

if HAS_PYLXD:
    import pylxd.exceptions  # NOQA

    if not hasattr(pylxd.exceptions, "NotFound"):
        # Old version of pylxd

        class NotFound(pylxd.exceptions.LXDAPIException):
            """An exception raised when an object is not found."""

        pylxd.exceptions.NotFound = NotFound

    try:
        from pylxd.container import Container
    except ImportError:
        from pylxd.models.container import Container

    class FilesManager(Container.FilesManager):
        def put(self, filepath, data, mode=None, uid=None, gid=None):
            headers = {}
            if mode is not None:
                if isinstance(mode, int):
                    mode = oct(mode)
                elif not mode.startswith("0"):
                    mode = "0{0}".format(mode)
                headers["X-LXD-mode"] = mode
            if uid is not None:
                headers["X-LXD-uid"] = six.text_type(uid)
            if gid is not None:
                headers["X-LXD-gid"] = six.text_type(gid)
            response = self._client.api.containers[self._container.name].files.post(
                params={"path": filepath}, data=data, headers=headers
            )
            return response.status_code == 200

    Container.FilesManager = FilesManager
